package cc.jinglupeng.wechat.api;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;

import cc.jinglupeng.wechat.AccountCache;
import cc.jinglupeng.wechat.WechatCacheManager;
import cc.jinglupeng.wechat.bean.AccessToken;
import cc.jinglupeng.wechat.bean.Account;
import cc.jinglupeng.wechat.bean.media.Media;
import cc.jinglupeng.wechat.bean.media.MediaId;
import cc.jinglupeng.wechat.consts.MediaType;
import cc.jinglupeng.wechat.util.WxHttpUtils;

/**
 * 基础API
 * 
 * @author jinglupeng.cc
 */
public class BaseAPI {

	private static Logger logger = Logger.getLogger(BaseAPI.class);

	private final static String UPLOAD_MEDIA_URL = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE";
	private final static String DOWN_MEDIA_URL = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID";
	public static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

	/**
	 * 直接从微信公众平台获取AccessToken
	 * 
	 * @param appId
	 *            -第三方用户唯一凭证
	 * @param appSecret
	 *            -第三方用户唯一凭证密钥，即appsecret
	 * @return
	 */
	public static AccessToken getAccessTokenFromServer(String appId,
			String appSecret) {
		String url = ACCESS_TOKEN_URL.replaceAll("APPID", appId).replaceAll(
				"APPSECRET", appSecret);
		AccessToken token = WxHttpUtils.get(url, AccessToken.class);
		if (token.isSuccess()) {
			token.setCreateTime(System.currentTimeMillis());
			WechatCacheManager.cacheAccessToken(appId, token);
			return token;
		}
		logger.error("获取AccessToken失败，AppId:" + appId + ",错误码："
				+ token.getErrcode());
		return new AccessToken(-2, "获取AccessToken失败");
	}

	/**
	 * 获取AppSecret，为了减少AppSecret在系统中不必要的暴露，对AppSecret进行统一管理
	 * 
	 * @param appId
	 *            -第三方用户唯一凭证
	 * @return
	 */
	protected synchronized static String getAppSecret(String appId) {
		Account account = AccountCache.get(appId);
		if (account == null) {
			logger.error("未找到appId[" + appId + "]的信息！");
			return null;
		}
		return account.getAppSecret();
	}

	/**
	 * 获取AccessToken，优先从缓存获取。获取AccessToken接口每天可获取2000次，每次有效期7200秒，
	 * 获取新的AccessToken后旧的立刻失效。
	 * 所以需要对AccessToken进行缓存，如果多个系统需要同时调用微信接口，则必须对AccessToken进行统一管理
	 * 
	 * @param appId
	 *            -第三方用户唯一凭证
	 * @return
	 */
	public synchronized static String getAccessToken(String appId) {
		AccessToken cacheToken = WechatCacheManager.getAccessToken(appId);
		if (cacheToken != null)
			return cacheToken.getAccess_token();
		String appSecret = getAppSecret(appId);
		if (appSecret == null) {
			logger.error("获取Appsecret失败，AppId:" + appId);
			return null;
		}
		AccessToken accessToken = getAccessTokenFromServer(appId, appSecret);
		if (!accessToken.isSuccess()) {
			logger.error("从微信服务器获取Appsecret失败，AppId:" + appId);
			return null;
		}
		logger.debug("Token:" + accessToken.getAccess_token());
		return accessToken.getAccess_token();
	}

	/**
	 * 上传多媒体文件到微信服务器
	 * 
	 * @param appId
	 *            -第三方用户唯一凭证
	 * @param file
	 *            -文件
	 * @param type
	 *            -媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
	 * @return
	 */
	public static MediaId uploadMedia(String appId, File file, MediaType type) {
		if (!type.checkSizeLimit(file)) {
			logger.error("上传多媒体文件失败，文件大小超过限制。限制为： " + type.getLimitSize()
					+ "。文件地址：" + file.getAbsolutePath());
			return new MediaId(-3, "上传文件大小超过限制！");
		}
		String token = getAccessToken(appId);
		if (token == null) {
			logger.error("上传多媒体文件失败，获取AccessToken失败！");
			return new MediaId(-4, "获取AccessToken失败！");
		}
		String url = UPLOAD_MEDIA_URL.replace("ACCESS_TOKEN", token).replace(
				"TYPE", type + "");
		return WxHttpUtils.uploadMedia(url, file, MediaId.class);
	}

	/**
	 * 从微信服务器下载多媒体文件，该方法将文件数据缓存到内存中不做进一步处理，因此当从附件对象中读取数据结束后应该迅速将附件对象置空。
	 * 
	 * @param appId
	 *            -第三方用户唯一凭证
	 * @param mediaId
	 *            -媒体文件ID
	 * @return
	 */
	public static Media downMedia(String appId, String mediaId) {
		String token = getAccessToken(appId);
		if (token == null) {
			logger.error("下载多媒体文件失败，获取AccessToken失败！");
			return new Media(-4, "获取AccessToken失败！");
		}
		String url = DOWN_MEDIA_URL.replace("ACCESS_TOKEN", token).replace(
				"MEDIA_ID", mediaId);
		try {
			return WxHttpUtils.downloadMedia(url);
		} catch (IOException e) {
			logger.error("下载多媒体文件失败，错误信息：" + e.getMessage());
			return new Media(-5, "未知失败！");
		}
	}

	/**
	 * 用于和微信公众平台对接，验证消息是否来自微信公众平台
	 * 
	 * @param token
	 * @param signature
	 *            -微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
	 * @param timestamp
	 *            -时间戳
	 * @param nonce
	 *            -随机数
	 * @return
	 */
	public static final boolean checkSignature(String token, String signature,
			String timestamp, String nonce) {
		List<String> params = new ArrayList<String>();
		params.add(token);
		params.add(timestamp);
		params.add(nonce);
		Collections.sort(params, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
		String temp = params.get(0) + params.get(1) + params.get(2);
		return DigestUtils.sha1Hex(temp).equals(signature);
	}
}
